package com.example.demo.startup;

import cn.hutool.core.date.StopWatch;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * 系统启动注解监听器
 */
@Slf4j
@Component
public class StartupListener implements ApplicationListener<ApplicationReadyEvent> {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        ConfigurableApplicationContext applicationContext = event.getApplicationContext();
        List<StartupBean> startupBeanList = new ArrayList<>();

        //获取所有spring容器管理的bean并遍历
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            //获取bean及其原始类的所有方法并遍历
            Object bean = applicationContext.getBean(beanDefinitionName);
            Class<?> targetClass = AopUtils.getTargetClass(bean);
            Method[] methods = targetClass.getMethods();
            for (Method method : methods) {
                //如果方法被@Startup注解标记，则加入startupBeanList
                if (method.isAnnotationPresent(Startup.class)) {
                    StartupBean startupBean = new StartupBean();
                    startupBean.setBean(bean);
                    startupBean.setMethod(method);
                    startupBean.setName(String.format("%s.%s()", targetClass.getSimpleName(), method.getName()));
                    startupBean.setOrder(method.getAnnotation(Startup.class).value());
                    startupBeanList.add(startupBean);
                }
            }
        }

        //对startupBeanList进行排序，并逐个触发执行
        startupBeanList.stream()
                .sorted(Comparator.comparingInt(StartupBean::getOrder))
                .forEach(StartupBean::invoke);
    }

    /**
     * 被Startup注解的方法的一个包装对象
     */
    @Data
    private static class StartupBean {
        private Integer order;
        private String name;
        private Method method;
        private Object bean;

        public void invoke() {
            StopWatch watch = new StopWatch();
            log.info("Startup {} invoke", this.name);
            watch.start();
            try {
                this.method.invoke(this.bean);
            } catch (Exception e) {
                log.error("Startup {} error {}", this.name, e);
            } finally {
                watch.stop();
            }
            log.info("Startup {} finished time={}ms", this.name, watch.getTotalTimeMillis());
        }
    }

}
